package com.neural.infrastructure;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import com.neural.descriptor.DataSetDescriptor;
import com.neural.descriptor.FeedDescriptor;
import com.neural.descriptor.LayerDescriptor;
import com.neural.descriptor.NetDescriptor;

public class Net {

	private final List<Layer> layers;
	private final double learningSpeed;

	public Net(NetDescriptor netDescriptor) {
		this.learningSpeed = netDescriptor.getLearningSpeed();
		this.layers = new ArrayList<Layer>(netDescriptor.getLayersDescriptor().size());
		Layer previousLayer = null;
		for (LayerDescriptor layerDescriptor : netDescriptor.getLayersDescriptor()) {
			previousLayer = new Layer(layerDescriptor, previousLayer);
			this.layers.add(previousLayer);
		}
	}
	
	public List<ResultPojo> feedNet(FeedDescriptor feedDescriptor) {
		
		List<ResultPojo> resultPojos = new LinkedList<ResultPojo>();
		for(DataSetDescriptor dataSetDescriptor : feedDescriptor.getDataSetDescriptors()) {
			
			Layer inputLayer = this.getInputLayer();
			inputLayer.setNeuronValues(dataSetDescriptor.getInputs());
			this.simplyPropagateValues();

			List<Double> expectedOutputs = dataSetDescriptor.getExpectedOutputs();
			resultPojos.add(new ResultPojo(dataSetDescriptor.getInputs(), 
										   this.getOutputValues(), 
										   expectedOutputs, 
										   this.simplyCalculateErrors(expectedOutputs)));
		}
		return resultPojos;
	}

	public List<Layer> getLayers() {
		return layers;
	}
	
	public Layer getInputLayer() {
		return layers.get(0);
	}
	
	private Layer getOutputLayer() {
		return layers.get(layers.size()-1);
	}
	
	private void simplyPropagateValues() {
		for(Layer layer : layers) {
			layer.compute();
		}
	}
	
	private List<Double> simplyCalculateErrors(List<Double> expectedOutputs) {
		List<Neuron> outputNeurons = getOutputLayer().getNeurons();
		List<Double> errors = new ArrayList<Double>(outputNeurons.size());
		for(int i = 0; i < outputNeurons.size(); i++) {
			errors.add(optimalizeWeigth(outputNeurons.get(i), expectedOutputs.get(i)));
		}
		return errors;
	}
	
	private double optimalizeWeigth(Neuron neuron, double expected) {
		return neuron.optimalizeWeigths(learningSpeed, expected);
	}

	private List<Double> getOutputValues() {
		return getOutputLayer().getValues();
	}
}
